__version__ = '0.7'
__author__  = 'ArmedGuy'

import b3, time, threading, re, socket, thread
from b3 import clients
import b3.events
import b3.plugin
import datetime, string
from b3 import functions

#--------------------------------------------------------------------------------------------------
class IrcbootPlugin(b3.plugin.Plugin):
    SpawnedPlugin = None
    _reColor = re.compile(r'(\^[0-9a-z])|[\x80-\xff]')
    _settings = { # default settings
        'irc': {
            'nick': "b3ircbot-ircboot",
            'ident': "b3ircboot",
            'realname': "Big Brother",
            'password': "",
            'host': None,
            'port': 6667
            'channels': None,
        },
        'relay': {
            'mode': 'rcon',
            'broadcasts': 'true',
            'gamechatmode': 'public',
            'gameeventmode': 'public',
        },
        'commands': {
            'ircadd': 80,
            'ircrem': 80,
            'ircexec': 100,
        },
        'users': {
            'userfile': None,
        },
    }
    _ircbot = None
    _adminPlugin = None
    def onLoadConfig(self):
        IrcBootPlugin.SpawnedPlugin = self
        if self._adminPlugin == None:
            try:
                self._adminPlugin = self.console.getPlugin('admin')
            except:
                self.error("Could not get admin plugin!")
                return False
            
        #----------------------------------- Bot Settings ---------------------------------------------        
        for section in self.config.sections():
            for setting in self.config.options(secion):
                try:
                    self._settings[section][setting] = self.config.get(section, setting)
                except:
                    if option in ('host', 'channels', 'userfile'):
                        self.error("Could not load setting '%s->%s', plugin cannot start without it!" % (section,setting))
                        return False
                    else:
                        self.warning("Could not load setting '%s->%s', plugin falls back on default!" % (section, setting))
        # ------------------------------------------------------ Start IRC Bot -----------------------------------------
        if self._ircbot:
            self._ircbot.exit()
        
        self.debug("Starting IRC bot with nick=%s, ident=%s, realname=%s, host=%s, port=%s" % (self._nick, self._ident, self._realname, self._host, str(self._port)))
        self._ircbot = IrcBot(self._nick, self._ident, self._realname, self._host, self._port)
        self._ircbot.threadLoop()
        if self._settings['irc']['password'] != "":
            self._ircbot.msg("NickServ", "IDENTIFY %s" % self._settings['irc']['password'])
        for chan in self._channels:
            self._ircbot.join(chan)
            self._ircbot.msg(chan, "IrcBoot B3 plugin v%s by %s started... now accepting commands!" % (__version__,__author__))    
        self._ircbot.defaultCommandHandler(self.run_b3_command)
            
    def startup(self):
        """\
        Initialize plugin settings
        """
        
        # listen for client events
        self.registerEvent(b3.events.EVT_CLIENT_SAY)
        self.registerEvent(b3.events.EVT_CLIENT_TEAM_SAY)
        self.registerEvent(b3.events.EVT_CLIENT_PRIVATE_SAY)
        
        
    def getIrcBot(self):
        if self._ircbot != None:
            return self._ircbot
        
    def run_b3_command(self, ircbot, user, chan, msg): # will probably pass user,chan as IrcClient class instead
        if chan[0] != "#": # only allow pm commands
            self.console.queueEvent(self.console.getEvent('EVT_CLIENT_SAY', msg, IrcClient(ircbot,"~%s"%user)))


    

    def onEvent(self, event):
        if not event.client or event.client.cid == None or len(event.data) <= 0:
            return
        
        if self._ircbot:
            if event.type == b3.events.EVT_CLIENT_SAY:
                for chan in self._channels:
                    self._ircbot.msg(chan, re.sub(self._reColor, '', "(ALL)%s: %s" % (event.client.name,event.data)))
            if event.type == b3.events.EVT_CLIENT_TEAM_SAY:
                for chan in self._channels:
                    self._ircbot.msg(chan, re.sub(self._reColor, '', "(TEAM)%s: %s" % (event.client.name,event.data)))
            if event.type == b3.events.EVT_CLIENT_PRIVATE_SAY:
                for chan in self._channels:
                    self._ircbot.msg(chan, re.sub(self._reColor, '', "(PM)%s: %s" % (event.client.name,event.data)))
      
      
class IrcAuthSystem:
    AuthenticationFile = ""
    AuthenticationMap = []
    LastFileLoad = 0
    @staticmethod
    def getLevel(userstring):
        if(time.time() - IrcAuthSystem.LastFileLoad > 600): IrcAuthSystem.loadUsers()
        for client in IrcAuthSystem.AuthenticationMap:
            if client[2].match(userstring) != None:
                return int(client[1])
        return 0
    @staticmethod
    def setLevel(userstring, level, add=False):
        IrcAuthSystem.loadUsers()
        for client in IrcAuthSystem.AuthenticationMap:
            if client[2].match(userstring) != None:
                client[1] = level
        if add == True:
            IrcAuthSystem.AuthenticationMap.push(userstring, level, re.compile(userstring.replace("*",".*")))
        
    @staticmethod
    def loadUsers():
        try:
            IrcAuthSystem.AuthenticationMap = []
            f = open(IrcAuthSystem.AuthenticationFile, "r")
            for line in f.readlines():
                if ":" in line:
                    d = line.split(":", 1)
                    IrcAuthSystem.AuthenticationMap.push(d[0],d[1],re.compile(d[0].replace("*",".*")))
        LastFileLoad = time.time()
        except:
            IrcbootPlugin.SpawnedEngine.error("IrcAuthSystem.loadUsers: could not load users!")
    @staticmethod
    def saveUsers():
        try:
            f = open(IrcAuthSystem.AuthenticationFile, "w")
            f.truncate(0)
            for client in IrcAuthSystem.AuthenticationMap:
                f.write("%s:%i\n" % (client[0],client[1]))
            f.close()
        except:
            IrcbootPlugin.SpawnedPlugin.error("IrcAuthSystem.saveUesrs: could not save users!")
            
        
class IrcBot:
    _client = "IrcBoot B3 Plugin"
    _version = "%s by %s" % (__version__,__author__)
    _env = "Python-x64"
    _nick = ""
    _ident = ""
    _realname = ""
    _host = ""
    _port = 6667
    _socket = None
    _doLoop = True
    _loopThread = None
    _defaultCommandHandler = None
    _commands = {}
    def __init__(self, nick, ident, realname, host, port=6667):
        self._nick = nick
        self._ident = ident
        self._realname = realname
        self._host = host
        self._port = port
        
        self._socket = socket.create_connection((self._host, self._port))
        
        self.out("NICK %s\r\n" % self._nick)
        self.out("USER %s %s bla :%s\r\n" % (self._ident, self._host, self._realname))
        
        self.registerCommand("broadcast", self.cmd_broadcast)

    def defaultCommandHandler(self, handler):
        self._defaultCommandHandler = handler
        
        
    def registerCommand(self, name, handler):
        self._commands[name] = handler
        
        
    def threadLoop(self):
        self._loopThread = thread.start_new_thread(self.loop, ())
        
        
    def loop(self):
        while self._doLoop:
            buf = self._socket.recv(256)
            if buf == "":
                self._doLoop = False
                return
            if "PING" in buf and buf[0] != ":":
                    self.out("PONG :%s\r\n" % self._host)
            if buf[0] == ":":
                if "PRIVMSG" in buf and " 005 " not in buf:
                    user, chan, msg = self.parseMessage(buf)
                    if msg[0] == "!" or msg[0] == "@":
                        self.handleCommand(user, chan, msg)
                    if "\x01VERSION\x01" in msg:
                        print "recieved version message, responding"
                        self.msg(user, "\x01VERSION %s:%s:%s\x01" % (self._client, self._version, self._env)) # not sure if that is the correct one
        
        
    def exit(self):
        if self._loopThread != None:
            self._doLoop = False
        self._socket.close()
        
    # basic send types
    def out(self, data):
        if len(data) == 0: return
        if "\n" not in data:
            data = data + "\n"
        if self._socket:
            self._socket.send(data)
            
    def msg(self, target, message):
        self.out("PRIVMSG %s :%s\r\n" % (target,message))
    
    
    # Channel stuff
    def join(self, channel):
        self.out("JOIN :%s\r\n" % channel)
        
    def leave(self, channel):
        self.out("PART :%s\r\n" % channel)
        
    # Other stuff
    def status(self, status):
        if status == "":
            self.out("NICK %s\r\n" % self._nick)
        else:
            self.out("NICK %s|%s\r\n" % self._nick, status)
        
    # Parse stuff    
    def parseMessage(self, buf):
        d = buf.split(':', 2)
        user = d[1].split('!')[0].strip()
        
        if "#" in d[1]:
            chan = "#" + d[1].split('#')[1].strip()
        else:
            chan = user.replace("~", "") # pm
            
        msg = d[2].strip()
        
        return (user,chan,msg)
        
        
    def parseCommand(self, msg):
        d = msg[1:].strip()
        if " " in d:
            return d.split(" ")[0], d.split(" ")[1:]
        else:
            return d, ()
        
    def handleCommand(self, user, chan, msg):
        if msg[0] != "!" and msg[0] != "@":
            return
        cmd, args = self.parseCommand(msg)
        
        if cmd in self._commands:
            self._commands[cmd](self, user, chan, msg)
        else:
            self._defaultCommandHandler(self, user, chan, msg)
            
            
            
    #!broadcast !say
    # some irc specific commands
        
        
    def cmd_broadcast(self, bot, user, chan, msg):
        self._defaultCommandHandler(self, user, user, msg[11:]) # send message as if it was from a user
        
        
        
        
class IrcClient(b3.clients.Client):
    Clients = []
    _reColor = re.compile(r'(\^[0-9a-z])|[\x80-\xff]')
    maxLevel = 0
    id = -2
    _userString = ""
    def GetClient(userstring):
        for client in IrcClient.Clients:
            if client._userString == userstring:
                return client
        return IrcClient(IrcbootPlugin.SpawnedPlugin.getIrcBot(), userstring)
    
    def __init__(self, relaybot, userstring, channel=""): # will have to figure out their IRC rank before letting them be lvl 100
        IrcClient.Clients.push(self)
        self._userstring = userstring
        self.maxLevel = IrcAuthSystem.getLevel(userstring)
        self.name = userstring.split("!")[0]
        self.authed = True
            
    def getWrap(self, text, length=80, minWrapLen=150): # taken from b3 core, but without any colors, just removing them
        """Returns a sequence of lines for text that fits within the limits"""
        if not text:
            return []

        length = int(length)
        text = text.replace('//', '/ /')
        text = re.sub(self._reColor, '', text)
        if len(text) <= minWrapLen:
            return [text]
        #if len(re.sub(REG, '', text)) <= minWrapLen:
        #    return [text]

        text = re.split(r'\s+', text)

        lines = []

        line = text[0]
        for t in text[1:]:
            if len(re.sub(self._reColor, '', line)) + len(re.sub(self._reColor, '', t)) + 2 <= length:
                line = '%s %s' % (line, t)
            else:
                if len(lines) > 0:
                    lines.append(line)
                else:
                    lines.append(line)

                m = re.findall(self._reColor, line)
                if m:
                    color = m[-1]

                line = t

        if len(line):
            if len(lines) > 0:
                lines.append(line)
            else:
                lines.append(line)

        return lines
    def message(self, message):
        for line in self.getWrap(message):
            self._ircbot.msg(self.name, "[pm]: %s" % line)
